iT邦幫忙

2022 iThome 鐵人賽

DAY 7
2

Actions - Part 1

昨天我們透過簡單的範例來了解 Services 的基本綱目,而今天要來專注於說明 Actions 的規則與用法。

在服務中可以使用 Actions 來建立可公開呼叫的方法,並且通過遠端程序呼叫 (Remote Procedure Call, RPC) 與另一個程序進行互動。它就像 HTTP 一樣能夠對指定的對象發送參數請求,然後響應相應的內容。

如果你建立了多個實例, Broker 將會依負載平衡規則來請求服務:


Fig. 1. 負載平衡請求服務

服務呼叫

若要呼叫一個服務可以使用 broker.call 方法,當你使用這個方法時, Broker 會去找尋符合服務的動作 Action 並且呼叫它,然後回傳一個 Promise 物件。

範例:

const res = await broker.call(actionName[, params[, opts]]);

參數說明:

  • actionName <String> 是一個由點分隔的字串。依序為可選的版本號,使用的服務名稱,執行的動作名稱。
  • params <Object> 選填的物件型態自定義參數。可以透過 ctx.params 來取得參數內容,預設值為空物件 {}
  • opts <Object> 請求的相關內建參數設定。更多參數請參考下表:
名稱 類型 預設值 說明
timeout <Number> null 請求逾時設定(單位:毫秒)。
retries <Number> null 請求重試次數。
fallbackResponse <Any> null 請求失敗時的 Fallback 函數。
nodeID <String> null 目標節點。
meta <Object> {} 請求的 Metadata。可以使用 ctx.meta 來取用它。
parentCtx <Context> null 父層上下文,用於串接呼叫。
requestID <String> null 請求或關聯的 ID。用於追蹤。

使用方式

最簡單的方式就是直接呼叫:

const res = await broker.call("user.list");

也可以夾帶參數進行呼叫:

const res = await broker.call("user.get", { id: 3 });

帶有參數設定及 Fallback 函數的呼叫方式:

const res = await broker.call("user.recommendation", { limit: 5 }, {
    timeout: 500,
    retries: 3,
    fallbackResponse: defaultRecommendation
});

含有 Promise 異常處理函數的使用方式:

broker.call("posts.update", { id: 2, title: "Modified post title" })
    .then(res => console.log("Post updated!"))
    .catch(err => console.error("Unable to update Post!", err));

直接呼叫指定節點的方式:

const res = await broker.call("$node.health", null, { nodeID: "node-21" })

Meta 資訊

在 Actions 中的 Meta 屬性是用來儲存功能相關的資訊,可以通過 ctx.meta 來取用它。但在函數巢狀呼叫的情況下, meta 資訊是會被合併在一起的,在呼叫後返回也會保持合併過後的資料。

broker.createService({
    name: "farm",
    actions: {
        async greenhouse(ctx) {
            console.log(ctx.meta);
            // { tool: 'Shovel' }
            const result = await ctx.call("farm.container", null, {
                meta: {
                    plant: "Rose"
                }
            });
            console.log(ctx.meta);
            // { tool: 'Shovel', plant: 'Rose' }
            return result;
        },
        async container(ctx) {
            console.log(ctx.meta);
            // { tool: 'Shovel', plant: 'Rose' }
            return ctx.meta;
        }
    }
});

broker.call("farm.greenhouse", null, {
    meta: {
        tool: "Shovel"
    }
});

如果使用的是內部呼叫的方式,記得要加上 parentCtx 來傳遞 meta 資訊。

this.actions.container(ctx.params, { parentCtx: ctx });

逾時

逾時也可以在 Actions 設定。逾時設定的優先層級依序為:Service 設定、Actions 設定、call 函數設定,當後者被設定後將會取代前者。

範例:

const brokerNode = new ServiceBroker({
    nodeID: "node-1",
    requestTimeout: 5000, // 在 Service 設定
});
brokerNode.createService({
    name: "greeter",
    actions: {
        normal: {
            async handler() {
                return "Normal";
            }
        },
        slow: {
            timeout: 3000, // 在 Action 設定
            async handler() {
                return "Slow";
            }
        },
    }
});

呼叫範例:

// 5 秒
await broker.call("greeter.normal");
// 3 秒
await broker.call("greeter.slow");
// 1 秒
await broker.call("greeter.slow", null, {
    timeout: 1000 // 在 call 函數設定
});

多個呼叫

除了逐個呼叫 Action 外,可以使用 mcall 一次對多個 Actions 呼叫。

陣列型態的 mcall 呼叫方式:

await broker.mcall(
    [
        {
            action: 'posts.find', params: { author: 1 }, options: {
                // 個別選項設定
            }
        },
        {
            action: 'users.find', params: { name: 'John' }
        }
    ],
    {
        // 通用選項設定
        meta: { token: '63f20c2d-8902-4d86-ad87-b58c9e2333c2' }
    }
);

物件型態的 mcall 呼叫方式:

await broker.mcall(
    {
        posts: {
            action: 'posts.find', params: { author: 1 }, options: {
                // 個別選項設定
            }
        },
        users: {
            action: 'users.find', params: { name: 'John' }
        }
    },
    {
        // 通用選項設定
        meta: { token: '63f20c2d-8902-4d86-ad87-b58c9e2333c2' }
    }
);

串流

Moleculer 支援 Node.js 的 stream 可作為請求的參數也可以響應。也支援將由閘道器 (Gateway) 傳入的串流檔案進行編碼/解碼或是壓縮/解壓縮。

範例:
接收一個串流檔案。

注意,進行串流傳輸時 ctx.params 將會是一個串流資料。

module.exports = {
    name: "storage",
    actions: {
        save(ctx) {
            const stream = fs.createWriteStream(`/tmp/${ctx.meta.filename}`);
            ctx.params.pipe(stream);
        }
    }
};

範例:
返回一個串流檔案

module.exports = {
    name: "storage",
    actions: {
        get: {
            params: {
                filename: "string"
            },
            handler(ctx) {
                return fs.createReadStream(`/tmp/${ctx.params.filename}`);
            }
        }
    }
};

範例:
上傳一個串流檔案至其它服務。

注意,傳遞串流時請不要對 ctx.params 參雜任何其它的參數,如有需要夾帶其它相關資訊請使用 meta 資訊來傳遞,或是直接傳遞 parentCtx

upload: {
	async handler(ctx) {
		await this.broker.call(
			"storage.save",
			ctx.params,
			{ meta: { filename: "avatar.jpg" } },
			// { parentCtx: ctx },
		);
	}
},

範例:
取得遠端串流檔案儲存於本地。例如用於下次取得檔案時,可以直接從本地取得檔案。需要提供下載檔案名稱時,可以由 ctx.meta.$responseHeaders 來設定標頭資訊。

get: {
	rest: {
		method: "GET",
		path: "/file"
	},
	params: {
		filename: "string"
	},
	async handler(ctx) {
		const { filename } = ctx.params;
		// 檔案名稱
		ctx.meta.$responseHeaders = {
			"Content-Disposition": `attachment; filename*=utf-8''${encodeURIComponent(filename)}`
		};
		if (fs.existsSync(`./${filename}`)) {
			return fs.createReadStream(`./${filename}`);
		} else {
			const stream = await this.broker.call("storage.get", { filename });
			// 檔案可以在本地儲存
			const s = fs.createWriteStream(`./${filename}`);
			stream.pipe(s);
			s.on("close", () => this.broker.logger.info("File has been received"));
			return stream;
		}
	}
}

範例:
AES 編碼/解碼服務

const crypto = require("crypto");
const password = "moleculer";

module.exports = {
    name: "aes",
    actions: {
        encrypt(ctx) {
            const encrypt = crypto.createCipher("aes-256-ctr", password);
            return ctx.params.pipe(encrypt);
        },

        decrypt(ctx) {
            const decrypt = crypto.createDecipher("aes-256-ctr", password);
            return ctx.params.pipe(decrypt);
        }
    }
};

參考文獻

[1] Actions, https://moleculer.services/docs/0.14/actions.html

家家酒小劇場

  • Otter - 怎麼在 Actions 中也有 meta ,在 Service 中也有 meta 呢?
  • Boxy - 其實一般情況下 Actions 中的 ctx.meta 資料並不會影響 Service 的 this.metadata ,可以同時使用唷。

上一篇
Day 6 : Services
下一篇
Day 8 : Actions - Part 2
系列文
Moleculer 家家酒31
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

1 則留言

0
json_liang
iT邦研究生 5 級 ‧ 2022-09-07 10:58:49

Action 配合 nodejs 的 stream api !是個很強大的功能!

我要留言

立即登入留言